﻿using System;
using System.Collections.Generic;
namespace rehot.plugin
{
#if NETSTANDARD
    using System.Runtime.Loader;
    using System.Reflection;
    using System.IO;
    using SanlieHash;

    public partial class RehotAssembly
    {
        internal WeakReference<HotObject> weak;
        internal HotObject context;

        internal Dictionary<string, Assembly> _asms = new Dictionary<string, Assembly>();
        internal RehotAssemblyContainer _container;
        public RehotAssembly(string name)
        {
            this._container = new RehotAssemblyContainer(this);
            var _hottype = new HotType(typeof(AssemblyLoadContext));
            context = _hottype.New(HotParam.Put(name), HotParam.Put(true));
            weak = new WeakReference<HotObject>(context, true);
        }
        public void Unload()
        {
            if (_asms != null)
            {
                _asms.Clear();
                _asms = null;
            }
            if (_container != null)
            {
                _container.Lose();
                _container = null;
            }
            const string S = "Unload";
            if (context.Type.Methods.Exist(S))
            {
                context.Call(S);
                context = null;
                for (int i = 0; weak.TryGetTarget(out var _) && i < 32; i++)
                {
                    GC.Collect();
                    GC.WaitForPendingFinalizers();
                }
            }
        }

        public void Load(string path)
        {
            const string S = "LoadFromAssemblyPath";

            var name = ReadName(path);
            var key = FastHash.GetSM3(name.ToLower());

            if (!_asms.ContainsKey(key))
            {
                Assembly _asm;
                if (context.Type.Methods.Exist(S))
                    _asm = context.Call<Assembly>(S, HotParam.Put(path));
                else _asm = Assembly.LoadFile(path);
                _asms[key] = _asm;
            }
        }
        public void Load(Stream stream)
        {
            const string S = "LoadFromStream";
            if (context.Type.Methods.Exist(S))
            {
                var _asm = context.Call<Assembly>(S, HotParam.Put(stream));
                var name = _asm.GetName().Name;
                var key = FastHash.GetSM3(name.ToLower());
                if (!_asms.ContainsKey(key)) _asms[key] = _asm;
            }
            else if (HotType.Is<MemoryStream>(stream, out var mem)) Load(mem.ToArray());
            else
            {
                var buf = new byte[stream.Length];
                stream.Position = 0;
                stream.Read(buf, 0, buf.Length);
                Load(buf);
            }
        }
        public void Load(byte[] buf)
        {
            const string S = "LoadFromStream";
            if (context.Type.Methods.Exist(S))
                using (var ms = new MemoryStream(buf)) Load(ms);
            else
            {
                var _asm = Assembly.Load(buf);
                var name = _asm.GetName().Name;
                var key = FastHash.GetSM3(name.ToLower());

                if (!_asms.ContainsKey(key)) _asms[key] = _asm;
            }
        }
        public RehotAssemblyContainer GetContainer()
        {
            return _container;
        }

        public static bool Allow { get { return !HotType.IsAbstract<AssemblyLoadContext>(); } }
    }
#endif
#if NET20
    using System.Security;
    using System.Security.Permissions;
    using System.Reflection;
    using System.IO;
    using SanlieHash;

    [Serializable]
    internal class RehotAssemblyLoader : MarshalByRefObject
    {
        internal Dictionary<string, Assembly> _asms = new Dictionary<string, Assembly>();
        internal RehotAssemblyContainer _container;
        public RehotAssemblyLoader()
        {
            _container = new RehotAssemblyContainer(this);
        }
        public void Lose()
        {
            if (_container != null)
            {
                _container.Lose();
                _container = null;
            }
            if (_asms != null)
            {
                _asms.Clear();
                _asms = null;
            }
        }
        public void Load(string assemblyPath)
        {
            if (!File.Exists(assemblyPath))
                throw new FileNotFoundException(assemblyPath);

            var name = RehotAssembly.ReadName(assemblyPath);
            var key = FastHash.GetSM3(name.ToLower());
            if (!_asms.ContainsKey(key))_asms[key] = Assembly.LoadFrom(assemblyPath);
        }
        public void Load(byte[] buf)
        {
            var _asm = Assembly.Load(buf);
            var name = _asm.GetName().Name;
            var key = FastHash.GetSM3(name.ToLower());
            if (!_asms.ContainsKey(key))_asms[key] = _asm;
        }
        public RehotAssemblyContainer GetContainer()
        {
            return _container;
        }
    }
    public partial class RehotAssembly : MarshalByRefObject
    {
        internal AppDomain context { get; set; }
        internal RehotAssemblyLoader proxy { get; set; }
        internal string pluginDir { get; set; }
        public RehotAssembly(string name)
        {
            pluginDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "private");
            var setup = new AppDomainSetup
            {
                ApplicationName = name,
                ApplicationBase = AppDomain.CurrentDomain.BaseDirectory,
                PrivateBinPath = pluginDir,
                ShadowCopyFiles = "true", 
                ShadowCopyDirectories = pluginDir,
                CachePath = Path.Combine(pluginDir, "shadow"),
            };
            context = AppDomain.CreateDomain(name, null, setup);
            var xx =context.IsDefaultAppDomain();

            var proxyname = Assembly.GetExecutingAssembly().GetName().FullName;
            var proxyType = typeof(RehotAssemblyLoader);
            proxy = (RehotAssemblyLoader)context.CreateInstance(proxyType.Assembly.FullName, proxyType.FullName).Unwrap();
        }
        public void Unload()
        {
            proxy.Lose();
            proxy = null;
            AppDomain.Unload(this.context);
            context = null;
            for (int i = 0;i < 2; i++)
            {
                GC.Collect();
                GC.WaitForPendingFinalizers();
            }
        }
        public RehotAssemblyContainer GetContainer()
        {
            return proxy.GetContainer();
        }
        public void Load(string path)
        {
            proxy.Load(path);
        }
        public void Load(byte[] buf)
        {
            proxy.Load(buf);
        }
        public static bool Allow
        {
            get
            {
                try { return SecurityManager.IsGranted(new SecurityPermission(SecurityPermissionFlag.ControlAppDomain)); }
                catch { return false; }
            }
        }
    }
#endif

    public partial class RehotAssembly
    {
        public static string ReadName(string dll_path)
        {
            var name = AssemblyName.GetAssemblyName(dll_path).Name;
            return name;
        }
        public static HotDLL GetDllByType<T>()
        {
            return GetDllByAssembly(typeof(T).Assembly);
        }
        public static HotDLL GetDllByAssembly(Assembly ass)
        {
            return new HotDLL(ass);
        }
    }
}
